#include "World.h"
#include "Object.h"
#include "camera.h"
#include "Ray.h"
#include "Grid.h"
#include "KDTree.h"
#include "texture.h"
#include "Material.h"
#include "Result.h"
#include "GlobalDefine.h"
#include "tracer.h"
#include "sampler.h"
#include "BaseAcc.h"
#include <iostream>
#include <cmath>
#include <limits>
#include <omp.h>
#include <fstream>
#include <sstream>
#include "RapidXML/rapidxml.hpp"
#include "NoneAcc.h"
#include "AreaLight.h"
#include "Mesh.h"

namespace mray{

void World::RenderArea(uint camId, const int startX, const int startY, const int areaX, const int areaY)
{
	auto cam = GetCamera(camId);
	//multi threads capability
	int borderX = std::min(m_width, startX+areaX);
	int borderY = std::min(m_height, startY+areaY);
	const vec2 half(m_width/2.0f, m_height/2.0f);

#pragma omp parallel for
	for(int x = startX; x<borderX; x++)
#pragma omp parallel for
		for(int y = startY; y<borderY; y++){
			vec3 color = BLACK;
			for(int s = 0; s < m_pAASpl->m_nSamples; s++){
				const vec2& spp = (*m_pAASpl)[s];
				vec2 fp( (x - half.x + spp.x)/half.y, (y - half.y + spp.y)/half.y );
				//direction here is normalized
				Ray ray = cam.CastRay(fp);
				color += m_pTracer->Trace(ray, m_depth);
			}
			m_data[y*m_width + x] = (color /= (float)m_pAASpl->m_nSamples);
		}
}

bool World::Hit( Ray& ray, Result* pResult)const{
	return m_pAcc->Hit(ray, pResult);
}

vec3 World::GetBgColor(const vec3& direction)const{
	return m_pBgTex->GetColor(uvw2uv(direction));
}

void World::Initialize(const std::string& scenePath)
{
	Parse(scenePath);
	//default object mtl
	m_pDefMtl = new LambertMaterial;
	//if no tex, then asign the def mtl
	for(auto pObj : m_objList)
		if(!pObj->m_pMtl)
			pObj->m_pMtl = m_pDefMtl;
	//default light mtl
	m_pDefLigMtl = new EmissiveMaterial;
	for(auto pLig : m_areaLigList)
		pLig->m_pMtl = m_pDefLigMtl;

	//create a tmp collible array to build accelerator
	std::vector<Collible*> collibleArray;
	for(Collible* obj : m_objList){
		Mesh* mesh = dynamic_cast<Mesh*>(obj);
		if(mesh){
			auto& tris = mesh->m_triangles;
			for(auto& tri : tris) collibleArray.push_back(&tri);
		}
		else{
			collibleArray.push_back(obj);
		}
	}

	m_pAcc->Build(collibleArray);
	//fragments
	m_data = new vec3[m_width * m_height];
	m_rgb888 = new unsigned char[m_width * m_height * 3];
}

World::~World(){
	if(m_pDefMtl)		delete m_pDefMtl;
	if(m_pDefLigMtl)	delete m_pDefLigMtl;

	if(m_data)		delete [] m_data;
}

World::World(){
	m_pDefMtl		= nullptr;
	m_pDefLigMtl	= nullptr;
	m_data			= nullptr;
	m_rgb888		= nullptr;
}

vec3 World::Illuminate(const Result& result)const
{
	vec3 color = BLACK;
	for(auto& light : m_ligList)
		color += light->Illuminate(result);
	for(auto& area : m_areaLigList)
		color += area->Illuminate(result);
	return color;
}

uchar* World::Render(uint camId)
{
	if(m_renderArea)
		RenderArea(camId, m_startX, m_startY, m_areaX, m_areaY);
	else
		RenderArea(camId, 0, 0, m_width, m_height);

	//transform from float to uchar
	PostProcess();
	return m_rgb888;
}

void World::PostProcess()
{
	int end = m_width*m_height;
#pragma omp parallel for
	for(int i = 0; i < end; i++){
		m_rgb888[3*i  ] = Clamp((int)(m_data[i].r*256.f), 0, 255);
		m_rgb888[3*i+1] = Clamp((int)(m_data[i].g*256.f), 0, 255);
		m_rgb888[3*i+2] = Clamp((int)(m_data[i].b*256.f), 0, 255);
	}
}

}